//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2023-03-01     ZhangYihua   first version
//
// Description  : 
//################################################################################

// FIR filter by product pipeline, can't be optimised for sym_coe
module fir_pp #(
parameter           CDW                     = 16,
parameter           CFW                     = 15,
parameter           IDW                     = 12,
parameter           IFW                     = 9,
parameter           ODW                     = 12,
parameter           OFW                     = 9,
parameter           TAP_NUM                 = 15,       // must TAP_NUM>=2
parameter           COPY_NUM                = 1,        // COPY_NUM>=1, the number of in_dat copy for fan-out
parameter           VLD_USE                 = 1'b1,
parameter           IN_REG_EN               = 1'b1,
parameter           P_REG_EN                = 1'b1
) ( 
input                                       rst_n,
input                                       clk,

input                                       in_vld,
input               [TAP_NUM*CDW-1:0]       in_coe,     // {CN-1, .... C1, C0}
input       signed  [IDW-1:0]               in_dat,

output                                      out_vld,
output      signed  [ODW-1:0]               out_dat
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          PRDCT_NUM               = TAP_NUM;
localparam          PRDCT_DW                = IDW+CDW;
localparam          SUM_DW                  = PRDCT_DW+$clog2(PRDCT_NUM);
localparam          IDX_BW                  = (COPY_NUM==1) ? 1 : $clog2(COPY_NUM);

reg         signed  [IDW-1:0]               copy_dat[COPY_NUM-1:0];
wire                                        copy_vld;
wire                [TAP_NUM*IDX_BW-1:0]    idx_ary;
wire        signed  [SUM_DW-1:0]            cscd_ary[TAP_NUM:0];

//################################################################################
// main
//################################################################################

generate if (VLD_USE==1'b1) begin:G_VLD_DLY
    wire                                        vld_ip;
    wire                                        vld_pp;

    pipe_wrap #(.DW(1), .REG_EN(1'b1     ), .INI(1'b0)) u_vld_copy (.rst_n(rst_n), .clk(clk), .cke(1'b1), .id(in_vld  ), .od(copy_vld));
    pipe_wrap #(.DW(1), .REG_EN(IN_REG_EN), .INI(1'b0)) u_vld_ip   (.rst_n(rst_n), .clk(clk), .cke(1'b1), .id(copy_vld), .od(vld_ip  ));
    pipe_wrap #(.DW(1), .REG_EN(P_REG_EN ), .INI(1'b0)) u_vld_pp   (.rst_n(rst_n), .clk(clk), .cke(1'b1), .id(vld_ip  ), .od(vld_pp  ));
    pipe_wrap #(.DW(1), .REG_EN(1'b1     ), .INI(1'b0)) u_vld_out  (.rst_n(rst_n), .clk(clk), .cke(1'b1), .id(vld_pp  ), .od(out_vld ));

end else begin:G_NVLD
    assign copy_vld = 1'b1;
    assign out_vld = 1'b1;
end endgenerate

genvar g0;
generate for (g0=0; g0<COPY_NUM; g0=g0+1) begin:G_COPY
    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            copy_dat[g0] <=`U_DLY {IDW{1'b0}};
        end else if (in_vld==1'b1) begin
            copy_dat[g0] <=`U_DLY in_dat;
        end
    end
end endgenerate

assign idx_ary = alloc_idx_f(TAP_NUM, COPY_NUM);

assign cscd_ary[0] = {SUM_DW{1'b0}};

genvar g1;
generate for (g1=0; g1<TAP_NUM; g1=g1+1) begin:G_MUL_PP_ADD
    mul_add_cscd_wrap #(
        .CDW                            (CDW                            ),	// CDW<=19
        .IDW                            (IDW                            ),	// IDW<=18
        .VLD_USE                        (VLD_USE                        ),
        .PRE_ADD_EN                     (1'b0                           ),
        .IN_REG_EN                      (IN_REG_EN                      ),
        .P_REG_EN                       (P_REG_EN                       ),
        .CSCD_REG_EN                    (1'b1                           ),
        .CSCD_DW                        (SUM_DW                         ),
        .CSCD_HEAD                      (g1==0 ? 1'b1 : 1'b0            ),
        .CSCD_TAIL                      (g1==(TAP_NUM-1) ? 1'b1 : 1'b0  )
    ) u_mul_add_cscd (
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),

        .in_vld                         (copy_vld                       ),
        .in_coe                         (in_coe[g1*CDW+:CDW]            ),
        .in_dat                         (copy_dat[idx_ary[g1*IDX_BW+:IDX_BW]]),
        .in_dat_2                       ({IDW{1'b0}}                    ),

        .in_cscd                        (cscd_ary[g1]                   ),
        .out_cscd                       (cscd_ary[g1+1]                 )
    );

end endgenerate

s_sat_tru #(     // range [-(2^(IDW-1))/(2^IFW):(2^(IDW-1)-1)/(2^IFW)]
        .IDW                            (SUM_DW                         ),	// input data width
        .IFW                            (IFW+CFW                        ),	// input fractional width
        .ODW                            (ODW                            ),	// output data width
        .OFW                            (OFW                            )	// output fractional width
) u_out_dat ( 
        .id                             (cscd_ary[TAP_NUM]              ),	// s(IDW, IFW), the MSB is sign
        .od                             (out_dat                        ),	// s(ODW, OFW), the MSB is sign
        .over                           (                               )
);

function integer bit_sum_f;
    input [TAP_NUM-1:0]     vect;

    integer t;
    integer i;
    begin
        t = 0;
        for (i=0; i<TAP_NUM; i=i+1) begin
            if (vect[i]==1'b1) begin
                t = t + 1;
            end
        end

        bit_sum_f = t;
    end
endfunction

function [TAP_NUM*IDX_BW-1:0] alloc_idx_f;
    input integer t;
    input integer c;

    integer d, l;
    integer i, j, k;
    begin
        d = t/c;
        l = t%c;

        j = 0;
        k = (j<l) ? d+1 : d;
        for(i=0; i<t; i=i+1) begin
            if (i==k) begin
                j = j+1;
                k = k + ((j<l) ? d+1 : d);
            end
            alloc_idx_f[i*IDX_BW+:IDX_BW] = j;
        end
    end
endfunction

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule

